package nachos.filesys;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import nachos.filesys.DataManager.DirectoryItem;
import nachos.filesys.UFileSystem.UnodeManager;
import nachos.machine.Lib;
import nachos.machine.Machine;
import nachos.machine.OpenFile;
import nachos.vm.VMKernel;

public class FileSysKernel extends VMKernel{
	/**
     * Initialize this kernel. 
     */ 
    public void initialize(String[] args) {
    	super.initialize(args);
    }
    
    /**
     * start a shell programe in the kernel
     */
    @Override
    public void run(){
    	
		char c;
		String command;
		
		while(true){
			
			print("sylvia@nachos:/$ ");
			
			command = "";
			do {
				c = (char) console.readByte(true);
				command += c;
				//console.writeByte(c);
			}while (c != '\n');
			
			command = command.substring(0, command.length()-1);			
			if (!shell(command))
				println("invalid command \"" + command + "\"");
		}
    }
    
    /**
     * shell programme in the kernel
     * @param command
     */
    private boolean shell(String commandLine){
    	// get the exact command and the arguments
    	Pattern p = Pattern.compile("[-a-zA-Z.0-9_]+");
    	Matcher m = p.matcher(commandLine);
    	ArrayList<String> al = new ArrayList<String>();
    	while (m.find()){
    		String t = m.group();
    		al.add(t);
    	}
    	
    	// deal with the whole command line
    	String command = al.remove(0);
    	if (commandMap.containsKey(command)){
	    	int c = commandMap.get(command);
	    	switch (c){
	    	case c_ls:
	    		shLs(al);
	    		break;
	    	case c_cat:
	    		shCat(al);
	    		break;
	    	case c_cd:
	    		shCd(al);
	    		break;
	    	case c_exit:
	    		Machine.halt();
	    	case c_touch:
	    		shTouch(al);
	    		break;
	    	case c_rm:
	    		shRm(al);
	    		break;
	    	case c_rmdir:
	    		shRmdir(al);
	    		break;
	    	case c_mkdir:
	    		shMkdir(al);
	    		break;
	    	case c_edit:
	    		shEdit(al.get(0), commandLine);
	    		break;
	    	}	    	
	    	return true;
    	}else
    		return false;
    }
    
    private void shLs(ArrayList<String> args){
    	if (args.isEmpty()){
	    	int currentDirUnodeNum = UFileSystem.currentDirectoryUnodeNum;
	    	for (DirectoryItem i : DataManager.getInstance().getDirectoryItemByUnodeNum(currentDirUnodeNum)){
	    		if (i.unodeNum != -1)
	    		print(i.name + "\t");
	    	}
	    	println("");
    	}else{
    		if (args.get(0).equals("-l")){
    			println("name\ttype\tlimit\tcreate\tlast modify");
    			int currentDirUnodeNum = UFileSystem.currentDirectoryUnodeNum;
    	    	for (DirectoryItem i : DataManager.getInstance().getDirectoryItemByUnodeNum(currentDirUnodeNum)){
    	    		if (i.unodeNum != -1){
    	    			print(i.name + "\t");
    	    			Unode unode = UnodeManager.getUnode(i.unodeNum);
    	    			
    	    			if (unode.getFileType() == Unode.typeDirectory)
    	    				print("d");
    	    			else print("f");    	    			
    	    			print("\t");	
    	    			
    	    			if (unode.getReadLimit())
	    	    			print("r");
	    	    		else print("-");
	    	    		if (unode.getWriteLimit())
	    	    			print("w");
	    	    		else print("-");
	    	    		if (unode.getExecLimit())
	    	    			print("x");
	    	    		else print("-");
    	    			print("\t");
    	    			
	    	    		print(unode.getCreateTime() + "\t");
	    	    		print(unode.getLastModifyTime() + "\t");
	    	    		
	    	    		println("");
    	    		}
    	    	}
    	    	
    		}else{
    			
    		}
    	}
    	
    }
    
    private void shCat(ArrayList<String> args){
    	for (int i = 0; i < args.size(); i ++){
    		String name = args.get(i);
    		OpenFile of = fileSystem.open(name, false);
    		if (of != null){
    			of.seek(0);
    			byte[] buf = new byte[of.length()];
    			of.read(buf, 0, of.length());
    			println(Lib.bytesToString(buf, 0, buf.length));
    			of.close();
    		}else{
    			println("cat fail : file \"" + name + "\" does not exists or it's a directory!");
    			return;
    		}
    	}
    }
    
    private void shCd(ArrayList<String> args){
    	// only take the first argument
    	String arg = args.get(0);
    	//unode number of the target directory, or -1 for not found nor create
    	int un = UFileSystem.openDirectory(arg, false);
    	if (un == -1){
    		println("directory \"" + arg + "\" not found!");
    	}
    }
    
    private void shTouch(ArrayList<String> args){
    	for (int i = 0; i < args.size(); i ++){
    		if (args.get(i).indexOf("-") != -1)
    			continue;
    		String name = args.get(i);
    		OpenFile of = fileSystem.open(name, true);
    		of.close();
    	}
    }
    
    private void shRm(ArrayList<String> args){
    	for (int i = 0; i < args.size(); i ++){
    		String name = args.get(i);
    		boolean res = fileSystem.remove(name);
    		if (!res)
    			println("rm \"" + name + "\" error : not found or its a directory!");
    	}
    }
    
    private void shRmdir(ArrayList<String> args){
    	for (int i = 0; i < args.size(); i ++){
    		String name = args.get(i);
    		int res = UFileSystem.removeDir(name);
    		if (res == 1)
    			println("rmdir \"" + name + "\"error : directory not empty");
    		if (res  == -1)
    			println("rmdir \"" + name + "\"error : not exists");
    	}
    }
    
    private void shMkdir(ArrayList<String> args){
    	for (int i = 0; i < args.size(); i ++){
    		String name = args.get(i);
    		//return unode number of the target directory, or -1 for not found nor create
    		int res = UFileSystem.openDirectory(name, true);
    		if (res == -1)
    			println("mkdir failed : already exists");
    		UFileSystem.openDirectory("..", false);
    	}
    }
    
    private void shEdit(String fileName, String commandLine){
    	OpenFile of = fileSystem.open(fileName, false);
		if (of != null){
			of.seek(0);
			String content = new String(commandLine);
			content = content.substring(content.lastIndexOf(fileName) + fileName.length() + 1, content.length());
			System.out.println("content = " + content);
			of.write(content.getBytes(), 0, content.getBytes().length);
		}
		else{
			println("edit fail : file \"" + fileName + "\" does not exist");
		}
    			
    }
    
    private static final int
    	c_ls = 0,
    	c_cd = 1,
    	c_cat = 2,
    	c_touch = 3,
    	c_rm = 4,
    	c_rmdir = 5,
    	c_exit = 6,
    	c_mkdir = 7,
    	c_edit = 8;
    
    private static HashMap<String, Integer> commandMap = new HashMap<String, Integer>();
    static{
    	commandMap.put("ls", c_ls);
    	commandMap.put("cd", c_cd);
    	commandMap.put("cat", c_cat);
    	commandMap.put("touch", c_touch);
    	commandMap.put("rm", c_rm);
    	commandMap.put("rmdir", c_rmdir);
    	commandMap.put("exit", c_exit);
    	commandMap.put("mkdir", c_mkdir);
    	commandMap.put("edit", c_edit);
    }
    
    @Override
    public void selfTest() {
    	
    }
    
    @Override
    public void terminate(){
    	Machine.halt();
    }
    
    private void println(String str){
    	print(str);
    	console.writeByte('\n');
    }
    
    private void print(String str){
    	for (byte b : str.getBytes()){
    		console.writeByte(b);
    	}
    }
    
    
}
